package com.hy.video.monitor.conf.security;

import com.hy.video.monitor.conf.security.dto.LoginUser;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import javax.security.sasl.AuthenticationException;

public class SecurityUtils {

    /**
     * 描述根据账号密码进行调用security进行认证授权 主动调
     * 用AuthenticationManager的authenticate方法实现
     * 授权成功后将用户信息存入SecurityContext当中
     * @param username 用户名
     * @param password 密码
     * @param authenticationManager 认证授权管理器,
     * @see  AuthenticationManager
     * @return UserInfo  用户信息
     *
     * 1AuthenticationManager, 用户认证的管理类，所有的认证请求（比如login）都会通过提交一个token给AuthenticationManager
     * 的authenticate()方法来实现。当然事情肯定不是它来做，具体校验动作会由AuthenticationManager将请求转发给具体的实现类来做。
     * 根据实现反馈的结果再调用具体的Handler来给用户以反馈。这个类基本等同于shiro的SecurityManager。
     * 2AuthenticationProvider, 认证的具体实现类，一个provider是一种认证方式的实现，比如提交的用户名密码我是通过和DB中查出的user记录做比对实现的，
     * 那就有一个DaoProvider；如果我是通过CAS请求单点登录系统实现，那就有一个CASProvider。这个是不是和shiro的Realm的定义很像？基本上你可以帮他们当
     * 成同一个东西。按照Spring一贯的作风，主流的认证方式它都已经提供了默认实现，比如DAO、LDAP、CAS、OAuth2等。
     * 前面讲了AuthenticationManager只是一个代理接口，真正的认证就是由AuthenticationProvider来做的。
     * 一个AuthenticationManager可以包含多个Provider，每个provider通过实现一个support方法来表示自己支持那种Token的认证。
     * AuthenticationManager默认的实现类是ProviderManager。
     * 3UserDetailService, 用户认证通过Provider来做，所以Provider需要拿到系统已经保存的认证信息，获取用户信息的接口spring-security抽象成
     * UserDetailService。虽然叫Service,但是我更愿意把它认为是我们系统里经常有的UserDao。
     * 4AuthenticationToken, 所有提交给AuthenticationManager的认证请求都会被封装成一个Token的实现，比如最容易理解的UsernamePasswordAuthenticationToken。
     * 这个就不多讲了，连名字都跟Shiro中一样。
     * 5SecurityContext，当用户通过认证之后，就会为这个用户生成一个唯一的SecurityContext，里面包含用户的认证信息Authentication。通过SecurityContext我们可以获取到用户的标识Principle和授权信息GrantedAuthrity。在系统的任何地方只要通过SecurityHolder.getSecruityContext()就可以获取到SecurityContext。在Shiro中通过SecurityUtils.getSubject()到达同样的目的。
     * 我们大概通过一个认证流程来认识下上面几个关键的概念
     */
    public static LoginUser login(String username, String password, AuthenticationManager authenticationManager) throws AuthenticationException {
        //使用security框架自带的验证token生成器  也可以自定义。
        //我不知道用户名密码是不是对的，所以构造一个未认证的Token
        UsernamePasswordAuthenticationToken token =new UsernamePasswordAuthenticationToken(username,password);
        //Token给谁处理呢？当然是给当前的AuthenticationManager喽
        Authentication authenticate = authenticationManager.authenticate(token);
        //取出username和password提交给AuthenticationManager
        SecurityContextHolder.getContext().setAuthentication(authenticate);
        LoginUser user = (LoginUser) authenticate.getPrincipal();
        return user;
    }

    /**
     * 获取当前登录的所有认证信息
     * @return
     */
    public static Authentication getAuthentication(){
        SecurityContext context = SecurityContextHolder.getContext();
        return context.getAuthentication();
    }

    /**
     * 获取当前登录用户信息
     * @return
     */
    public static LoginUser getUserInfo(){
        Authentication authentication = getAuthentication();
        if(authentication!=null){
            Object principal = authentication.getPrincipal();
            if(principal!=null && !"anonymousUser".equals(principal)){
                LoginUser user = (LoginUser) authentication.getPrincipal();
                return user;
            }
        }
        return null;
    }

    /**
     * 获取当前登录用户ID
     * @return
     */
    public static int getUserId(){
        LoginUser user = getUserInfo();
        return user.getId();
    }

    /**
     * 生成BCryptPasswordEncoder密码
     *
     * @param password 密码
     * @return 加密字符串
     */
    public static String encryptPassword(String password) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.encode(password);
    }
}
